#include "cursor.h"
#include <stdlib.h>

//#include <wlr/types/wlr_seat.h>
//#include <wlr/types/wlr_pointer.h>
//#include <wlr/types/wlr_input_device.h>
#include <wlr/types/wlr_data_device.h>
#include "config.h"
#include "seat.h"

#if 1
static struct wlr_scene_node *
xytonode(double x, double y, struct wlr_surface **psurface,
        Client **pc, LayerSurface **pl, double *nx, double *ny, Server* server)
{
    struct wlr_scene_node *node, *pnode;
    struct wlr_surface *surface = NULL;
    Client *c = NULL;
    LayerSurface *l = NULL;
    const int *layer;
    int focus_order[] = { LyrOverlay, LyrTop, LyrFloat, LyrInputPopup, LyrTile,
        LyrBottom, LyrBg };

    for (layer = focus_order; layer < END(focus_order); layer++) {
        if ((node = wlr_scene_node_at(server->layers[*layer], x, y, nx, ny))) {
            if (node->type == WLR_SCENE_NODE_SURFACE)
                surface = wlr_scene_surface_from_node(node)->surface;
            /* Walk the tree to find a node that knows the client */
            for (pnode = node; pnode && !c; pnode = pnode->parent)
                c = pnode->data;
            if (c && c->type == LayerShell) {
                c = NULL;
                l = pnode->data;
            }
        }
        if (surface)
            break;
    }

    if (psurface) *psurface = surface;
    if (pc) *pc = c;
    if (pl) *pl = l;
    return node;
}
#endif
static void motionnotify(uint32_t time, Server* server)
{
    double sx = 0, sy = 0;
    struct wlr_surface *surface = NULL;
    struct wlr_drag_icon *icon;

    /* time is 0 in internal calls meant to restore pointer focus. */
    if (time) {
        wlr_idle_notify_activity(server->idle, server->seat->seat);

        /* Update selmon (even while dragging a window) */
        if (sloppyfocus)
            server->selmon = xytomon(server->cursor->cursor->x, server->cursor->cursor->y, server->output_layout);
    }

    if (server->seat->seat->drag && (icon = server->seat->seat->drag->icon))
        wlr_scene_node_set_position(icon->data, server->cursor->cursor->x + icon->surface->sx,
                server->cursor->cursor->y + icon->surface->sy);
    /* If we are currently grabbing the mouse, handle and return */
    if (server->cursor_mode == CurMove) {
        /* Move the grabbed client to the new position. */
        resize(server->grabc, server->cursor->cursor->x - server->grabcx, server->cursor->cursor->y - server->grabcy,
                server->grabc->geom.width, server->grabc->geom.height, 1);
        return;
    } else if (server->cursor_mode == CurResize) {
        resize(server->grabc, server->grabc->geom.x, server->grabc->geom.y,
                server->cursor->cursor->x - server->grabc->geom.x,
                server->cursor->cursor->y - server->grabc->geom.y, 1);
        return;
    }

    /* Find the client under the pointer and send the event along. */
    Client *c = NULL;
    xytonode(server->cursor->cursor->x, server->cursor->cursor->y, &surface, &c, NULL, &sx, &sy, server);

//    /* If there's no client surface under the cursor, set the cursor image to a
//     * default. This is what makes the cursor image appear when you move it
//     * off of a client or over its border. */
    if (!surface && time)
        wlr_xcursor_manager_set_cursor_image(server->cursor->cursor_mgr, "left_ptr", server->cursor->cursor);

    pointerfocus(c, surface, sx, sy, time);
}

static void motionrelative(struct wl_listener *listener, void *data)
{
    Cursor *_ = wl_container_of(listener, _, cursor_motion);
#if 1
    /* This event is forwarded by the cursor when a pointer emits a _relative_
     * pointer motion event (i.e. a delta) */
    struct wlr_event_pointer_motion *event = data;
    /* The cursor doesn't move unless we tell it to. The cursor automatically
     * handles constraining the motion to the output layout, as well as any
     * special configuration applied for the specific input device which
     * generated the event. You can pass NULL for the device if you want to move
     * the cursor around without any input. */
    wlr_cursor_move(_->cursor, event->device, event->delta_x, event->delta_y);
    motionnotify(event->time_msec, _->server);
#endif
}

static void motionabsolute(struct wl_listener *listener, void *data)
{
    Cursor *_ = wl_container_of(listener, _, cursor_motion_absolute);
#if 1
    /* This event is forwarded by the cursor when a pointer emits an _absolute_
     * motion event, from 0..1 on each axis. This happens, for example, when
     * wlroots is running under a Wayland window rather than KMS+DRM, and you
     * move the mouse over the window. You could enter the window from any edge,
     * so we have to warp the mouse there. There is also some hardware which
     * emits these events. */
    struct wlr_event_pointer_motion_absolute *event = data;
    wlr_cursor_warp_absolute(_->cursor, event->device, event->x, event->y);
    motionnotify(event->time_msec, _->server);
#endif
}

static void buttonpress(struct wl_listener *listener, void *data)
{
    Cursor *_ = wl_container_of(listener, _, cursor_button);
#if 1
    struct wlr_event_pointer_button *event = data;
    struct wlr_keyboard *keyboard;
    uint32_t mods;
    Client *c;
    const Button *b;

    wlr_idle_notify_activity(_->server->idle, _->server->seat->seat);

    switch (event->state) {
    case WLR_BUTTON_PRESSED:
#if 1
        /* Change focus if the button was _pressed_ over a client */
        xytonode(_->cursor->x, _->cursor->y, NULL, &c, NULL, NULL, NULL, _->server);
        /* Don't focus unmanaged clients */
        if (c && !client_is_unmanaged(c))
            focusclient(c, 1);
#endif
        keyboard = wlr_seat_get_keyboard(_->server->seat->seat);
        mods = keyboard ? wlr_keyboard_get_modifiers(keyboard) : 0;
#if 0
        for (b = buttons; b < END(buttons); b++) {
            if (CLEANMASK(mods) == CLEANMASK(b->mod) &&
                    event->button == b->button && b->func) {
                b->func(&b->arg);
                return;
            }
        }
#endif
        break;
    case WLR_BUTTON_RELEASED:
        /* If you released any buttons, we exit interactive move/resize mode. */
        /* TODO should reset to the pointer focus's current setcursor */
        if (_->server->cursor_mode != CurNormal) {
            wlr_xcursor_manager_set_cursor_image(_->cursor_mgr, "left_ptr", _->cursor);
            _->server->cursor_mode = CurNormal;
            /* Drop the window off on its new monitor */
            _->server->selmon = xytomon(_->cursor->x, _->cursor->y, _->server->output_layout);
            setmon(_->server->grabc, _->server->selmon, 0);
            return;
        }
        break;
    }
    /* If the event wasn't handled by the compositor, notify the client with
     * pointer focus that a button press has occurred */
    wlr_seat_pointer_notify_button(_->server->seat->seat,
            event->time_msec, event->button, event->state);
#endif
}

void
axisnotify(struct wl_listener *listener, void *data)
{
    Cursor *_ = wl_container_of(listener, _, cursor_axis);
#if 1
    /* This event is forwarded by the cursor when a pointer emits an axis event,
     * for example when you move the scroll wheel. */
    struct wlr_event_pointer_axis *event = data;
    wlr_idle_notify_activity(_->server->idle, _->server->seat->seat);
    /* Notify the client with pointer focus of the axis event. */
    wlr_seat_pointer_notify_axis(_->server->seat->seat,
            event->time_msec, event->orientation, event->delta,
            event->delta_discrete, event->source);
#endif
}

void
cursorframe(struct wl_listener *listener, void *data)
{
    Cursor *_ = wl_container_of(listener, _, cursor_frame);
#if 1
    /* This event is forwarded by the cursor when a pointer emits an frame
     * event. Frame events are sent after regular pointer events to group
     * multiple events together. For instance, two axis events may happen at the
     * same time, in which case a frame event won't be sent in between. */
    /* Notify the client with pointer focus of the frame event. */
    wlr_seat_pointer_notify_frame(_->server->seat->seat);
#endif
}

Cursor *cursor_create(Server* server)
{
    Cursor* _ = ecalloc(1, sizeof(*_));
    _->server = server;
    /*
     * Creates a cursor, which is a wlroots utility for tracking the cursor
     * image shown on screen.
     */
    _->cursor = wlr_cursor_create();
    wlr_cursor_attach_output_layout(_->cursor, server->output_layout);

    /* Creates an xcursor manager, another wlroots utility which loads up
     * Xcursor themes to source cursor images from and makes sure that cursor
     * images are available at all scale factors on the screen (necessary for
     * HiDPI support). Scaled cursors will be loaded with each output. */
    _->cursor_mgr = wlr_xcursor_manager_create(NULL, 24);

    /*
     * wlr_cursor *only* displays an image on screen. It does not move around
     * when the pointer moves. However, we can attach input devices to it, and
     * it will generate aggregate events for all of them. In these events, we
     * can choose how we want to process them, forwarding them to clients and
     * moving the cursor around. More detail on this process is described in my
     * input handling blog post:
     *
     * https://drewdevault.com/2018/07/17/Input-handling-in-wlroots.html
     *
     * And more comments are sprinkled throughout the notify functions above.
     */
    wl_signal_add(&_->cursor->events.motion, &_->cursor_motion);
    _->cursor_motion.notify = motionrelative;

    wl_signal_add(&_->cursor->events.motion_absolute, &_->cursor_motion_absolute);
    _->cursor_motion_absolute.notify = motionabsolute;

    wl_signal_add(&_->cursor->events.button, &_->cursor_button);
    _->cursor_button.notify = buttonpress;

    wl_signal_add(&_->cursor->events.axis, &_->cursor_axis);
    _->cursor_axis.notify = axisnotify;

    wl_signal_add(&_->cursor->events.frame, &_->cursor_frame);
    _->cursor_frame.notify = cursorframe;

    return _;
}

void cursor_destroy(Cursor* cursor)
{
    wlr_xcursor_manager_destroy(cursor->cursor_mgr);
    wlr_cursor_destroy(cursor->cursor);
    free(cursor);
}
